
// BalanceBook includes
#include "bbAccount.h"
#include "bbSQLiteStorage.h"

#include "bbIEnumerationListener.h"

// Mozilla includes
#include <mozIStorageService.h>
#include <mozIStorageStatement.h>
#include <mozIStorageValueArray.h>
#include <mozStorageCID.h>
#include <mozStorageHelper.h>
#include <nsComponentManagerUtils.h>
#include <nsIDOMWindow.h>
#include <nsIFilePicker.h>
#include <nsIWindowWatcher.h>
#include <nsServiceManagerUtils.h>

/**
 * To log this module, set the following environment variable:
 *   NSPR_LOG_MODULES=bbSQLiteStorage:5
 */
#ifdef PR_LOGGING
static PRLogModuleInfo* gSQLiteStorage = nsnull;
#define TRACE(args) \
  PR_BEGIN_MACRO \
    if (!gSQLiteStorage) \
      gSQLiteStorage = PR_NewLogModule("bbSQLiteStorage"); \
    PR_LOG(gSQLiteStorage, PR_LOG_DEBUG, args); \
  PR_END_MACRO
#define LOG(args) \
  PR_BEGIN_MACRO \
    if (!gSQLiteStorage) \
      gSQLiteStorage = PR_NewLogModule("bbSQLiteStorage"); \
    PR_LOG(gSQLiteStorage, PR_LOG_WARN, args); \
  PR_END_MACRO
#else
#define TRACE(args) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
#define LOG(args)   PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
#endif

NS_IMPL_ISUPPORTS1(bbSQLiteStorage, bbIStorage)

bbSQLiteStorage::bbSQLiteStorage()
{
}

bbSQLiteStorage::~bbSQLiteStorage()
{
  // Close any open database
  this->closeDBConnection();
}

nsresult
bbSQLiteStorage::openDBConnection(nsILocalFile *aSqlFile)
{
  // Close any existing connections
  nsresult rv;
  rv = this->closeDBConnection();
  NS_ENSURE_SUCCESS(rv, rv);

  // Load the Mozilla SQLStorage
  nsCOMPtr<mozIStorageService> dbService =
      do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  // Open the database
  rv = dbService->OpenDatabase(aSqlFile, getter_AddRefs(mDbConn));
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult
bbSQLiteStorage::closeDBConnection()
{
  nsresult rv;
  if (mDbConn) {
    rv = mDbConn->Close();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

/**
 * bbIStorage
 */

/* readonly attribute AString storageTypeName; */
NS_IMETHODIMP
bbSQLiteStorage::GetStorageTypeName(nsAString& aName)
{
  TRACE((__FUNCTION__));
  aName = NS_LITERAL_STRING("SQLite");
  return NS_OK;
}

/* void createStorage(); */
NS_IMETHODIMP
bbSQLiteStorage::CreateStorage()
{
  TRACE((__FUNCTION__));
  return NS_OK;
}

/* void openStorage(); */
NS_IMETHODIMP
bbSQLiteStorage::OpenStorage()
{
  TRACE((__FUNCTION__));
  nsresult rv;

  nsCOMPtr<nsIFilePicker> filePicker =
      do_CreateInstance("@mozilla.org/filepicker;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIWindowWatcher> wwatch =
    (do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDOMWindow> window;
  wwatch->GetActiveWindow(getter_AddRefs(window));

  rv = filePicker->Init(window,
                        NS_LITERAL_STRING("Open Database"),
                        nsIFilePicker::modeOpen);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = filePicker->AppendFilter(NS_LITERAL_STRING("SQLite"),
                                NS_LITERAL_STRING("*.sqlite"));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = filePicker->AppendFilters(nsIFilePicker::filterAll);
  NS_ENSURE_SUCCESS(rv, rv);

  PRInt16 dialogResult;
  rv = filePicker->Show(&dialogResult);
  NS_ENSURE_SUCCESS(rv, rv);

  if (dialogResult == nsIFilePicker::returnCancel) {
    return NS_ERROR_ABORT;
  }

  nsCOMPtr<nsILocalFile> sqlFile;
  rv = filePicker->GetFile(getter_AddRefs(sqlFile));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = this->openDBConnection(sqlFile);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

/* void closeStorage(); */
NS_IMETHODIMP
bbSQLiteStorage::CloseStorage()
{
  TRACE((__FUNCTION__));
  nsresult rv;
  rv = this->closeDBConnection();
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

NS_IMETHODIMP
bbSQLiteStorage::EnumerateAllAccounts(bbIEnumerationListener *aListener)
{
  TRACE((__FUNCTION__));
  NS_ENSURE_ARG_POINTER(aListener);
  NS_ENSURE_TRUE(mDbConn, NS_ERROR_NOT_INITIALIZED);

  nsresult rv;
  PRUint16 keepGoing = bbIEnumerationListener::CONTINUE;

  nsCOMPtr<mozIStorageStatement> statement;
  // Only here for synchronous (remove when async)
  mozStorageStatementScoper scoper(statement);
  rv = mDbConn->CreateStatement(NS_LITERAL_CSTRING("SELECT account_id, account_name, account_type, account_balance FROM Accounts"),
                                getter_AddRefs(statement));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aListener->OnEnumerationBegin(&keepGoing);
  NS_ENSURE_SUCCESS(rv, rv);

  // TODO: Convert this to async
  PRBool hasMoreData;
  while (NS_SUCCEEDED(statement->ExecuteStep(&hasMoreData)) &&
         hasMoreData &&
         keepGoing == bbIEnumerationListener::CONTINUE) {
    // Get all the data
    PRInt32 accountID;
    rv = statement->GetInt32(0, &accountID);
    NS_ENSURE_SUCCESS(rv, rv);

    nsString accountName;
    rv = statement->GetString(1, accountName);
    NS_ENSURE_SUCCESS(rv, rv);

    nsString accountType;
    rv = statement->GetString(2, accountType);
    NS_ENSURE_SUCCESS(rv, rv);

    double accountBalance;
    rv = statement->GetDouble(3, &accountBalance);
    NS_ENSURE_SUCCESS(rv, rv);

    // Now create an account and set the data
    // TODO: Create this in a better way so we can set a callback to update
    //       the actual information in the database.
    // nsCOMPtr<bbIAccount> newAccount;
    // NEW_bbAccount(getter_AddRefs(newAccount), this);
    nsCOMPtr<bbIAccount> newAccount = do_CreateInstance(BB_ACCOUNT_CONTRACTID,
                                                        &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    newAccount->SetId(accountID);
    NS_ENSURE_SUCCESS(rv, rv);
    newAccount->SetName(accountName);
    NS_ENSURE_SUCCESS(rv, rv);
    newAccount->SetType(accountType);
    NS_ENSURE_SUCCESS(rv, rv);
    newAccount->SetBalance(accountBalance);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsISupports> supportsAccount = do_QueryInterface(newAccount, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = aListener->OnEnumeratedItem(supportsAccount, &keepGoing);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = aListener->OnEnumerationEnd(NS_OK);
  return rv;
}
